﻿using Microsoft.Owin.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Jhong.FunMVC
{
    internal sealed class MuilPartPostCollection
    {
        private Request _request;

        private static byte[] _newLine = Encoding.ASCII.GetBytes("\r\n");
        private static byte[] _dobuleNewLine = Encoding.ASCII.GetBytes("\r\n\r\n");

        private MuilPartPostCollection(Request requset)
        {
            this._request = requset;
        }

        private MuilPartElement[] _muilPartElements;


        private MuilPartElement[] GetMuilPartElements()
        {
            var boundary = this.GetBoundary();
            var tmpBuffer = new byte[this._request.ContentLength];
            var readOffset = 0;
            var contentLength = this._request.ContentLength;
            while (contentLength > 0)
            {
                var readLength = this._request.BodyStream.Read(tmpBuffer, readOffset, contentLength);
                if (readLength <= 0) break;
                readOffset += readLength;
                contentLength -= readLength;
            }
            var list = new List<MuilPartElement>();
            unsafe
            {
                fixed (byte* b = tmpBuffer)
                {
                    int curr = 0;
                    int mark = 0;
                    while (true)
                    {
                        if (curr == tmpBuffer.Length - 1) break;
                        if (*(b + curr) != boundary[0] || false == CheckBoundary(b, curr, boundary) || curr == 0)
                        {
                            curr++;
                            continue;
                        }
                        var length = curr - mark - boundary.Length;
                        if (length < 4) break;
                        var buff = new byte[length];
                        Array.Copy(tmpBuffer, mark + boundary.Length, buff, 0, length);
                        list.Add(this.ParseElement(buff));
                        if (curr == tmpBuffer.Length - 1) break;
                        mark = curr;
                        curr++;
                    }
                }
                return list.ToArray();
            }
        }

        private MuilPartElement ParseElement(byte[] buffer)
        {
            var tmp = 0;
            var pass = false;
            var offset = 0;
            string fileName = null;
            var name = string.Empty;
            var contentType = string.Empty;
            byte[] data = null;
            unsafe
            {
                fixed (byte* b = buffer) fixed (byte* nl = _newLine)
                {
                    // \r\n
                    var newLine = (short*)nl;

                    //1
                    var b1 = (short*)b;
                    while (true)
                    {
                        if (*(b1 + tmp) != *newLine) break;
                        tmp++;
                        offset += 2;
                    }
                    //2
                    tmp = offset;
                    while (true)
                    {
                        tmp = this.GetNextLine(b, tmp);
                        var descriptionBuff = new byte[tmp - offset];
                        Array.Copy(buffer, offset, descriptionBuff, 0, descriptionBuff.Length);
                        var descriptionStr = Encoding.ASCII.GetString(descriptionBuff);
                        var nameIndex = descriptionStr.IndexOf("name=", StringComparison.OrdinalIgnoreCase);
                        var nameEnd = descriptionStr.IndexOf(";", nameIndex);
                        name = nameEnd > 0 ? descriptionStr.Substring(nameIndex + 6, nameEnd - nameIndex - 7) : descriptionStr.Substring(nameIndex + 6, descriptionStr.Length - nameIndex - 7);
                        var fileNameIndex = descriptionStr.IndexOf("filename=", StringComparison.OrdinalIgnoreCase);
                        if (fileNameIndex > 0)
                        {
                            var fileNameEnd = descriptionStr.IndexOf(";", fileNameIndex);
                            fileName = fileNameEnd > 0 ? descriptionStr.Substring(fileNameIndex + 10, fileNameEnd - fileNameIndex - 10) : descriptionStr.Substring(fileNameIndex + 10, descriptionStr.Length - fileNameIndex - 11);
                        }
                        tmp += 2;
                        offset = tmp;
                        break;
                    }
                    //3
                    while (true)
                    {
                        tmp = this.GetNextLine(b, tmp);
                        var secondDescBuff = new byte[tmp - offset];
                        Array.Copy(buffer, offset, secondDescBuff, 0, secondDescBuff.Length);
                        var secondDescStr = Encoding.UTF8.GetString(secondDescBuff);
                        var contentTypeIndex = secondDescStr.IndexOf("Content-Type:");
                        if (contentTypeIndex >= 0)
                        {
                            var contentTypeEnd = secondDescStr.IndexOf(";", contentTypeIndex);
                            contentType = contentTypeEnd > 0 ? secondDescStr.Substring(contentTypeIndex + 13, contentTypeEnd - contentTypeIndex - 13) : secondDescStr.Substring(contentTypeIndex + 13, secondDescStr.Length - 13);
                        }
                        tmp += 4;
                        offset = tmp;
                        break;
                    }
                    //4
                    var dataLength = buffer.Length - offset ;
                    data = new byte[dataLength];
                    Array.Copy(buffer, offset, data, 0, data.Length);
                }
            }
            return new MuilPartElement(name, fileName, data, contentType);
        }

        private unsafe int GetNextLine(byte* b, int start)
        {
            var tmp = start;
            while (true)
            {
                if (*(b + tmp) != 13 || false == CheckBoundary(b, tmp, _newLine))
                {
                    tmp++;
                    continue;
                }
                return tmp;
            }
        }

        internal static unsafe bool CheckBoundary(byte* s, int curr, byte[] boundary)
        {
            fixed (byte* b = boundary)
            {
                var ls = (long*)(s + curr);
                var lb = (long*)b;
                var n = boundary.Length / 8;
                for (int i = 0; i < n; i++)
                    if (*(ls + i) != *(lb + i)) return false;
                for (int i = 8 * n; i < boundary.Length; i++)
                    if (*(s + curr + i) != *(b + i)) return false;
                return true;
            }
        }


        private byte[] GetBoundary()
        {
            var index = this._request.ContentType.IndexOf("boundary", StringComparison.OrdinalIgnoreCase);
            if (0 > index) return null;
            return Encoding.ASCII.GetBytes("--" + this._request.ContentType.Substring(index + 9));
        }

        internal MuilPartElement[] MuilPartElements
        {
            get
            {
                if (null != this._muilPartElements) return this._muilPartElements;
                this._muilPartElements = this.GetMuilPartElements();
                return this._muilPartElements;
            }
        }


        public static void Fill(Request request)
        {
            var mc = new MuilPartPostCollection(request);
            var elements = mc.MuilPartElements;
            var formSb = new StringBuilder();
            foreach (var item in elements)
            {
                if (null == item) continue;
                if (item.FileOrForm == FileOrForm.File)
                {
                    var pf = new HttpPostedFile(item.FileName, item.ContentType, item.GetDataAsByte());
                    request.Forms.Files.Add(item.Name, pf);
                }
                else
                {
                    formSb.Append(string.Format("&{0}={1}", item.Name, item.GetDataAsSTring()));
                }
            }
            if (formSb.Length > 0) formSb.Remove(0, 1);
            request.Forms.FormCollection = WebHelpers.ParseForm(formSb.ToString());
        }

    }

    internal sealed class MuilPartElement
    {

        public string Name { get; private set; }

        public string FileName { get; private set; }

        public FileOrForm FileOrForm
        {
            get
            {
                if (null == FileName) return FileOrForm.Form;
                return FileOrForm.File;
            }
        }

        public string ContentType { get; private set; }

        private byte[] _data;

        public MuilPartElement(string name, string fileName, byte[] data, string contentType)
        {
            this.Name = name;
            this.FileName = fileName;
            this._data = data;
            this.ContentType = ContentType;
        }

        public string GetDataAsSTring()
        {
            return Encoding.UTF8.GetString(this._data);
        }

        public byte[] GetDataAsByte()
        {
            return this._data;
        }


    }

    internal enum FileOrForm
    {
        File,
        Form
    }

}
